﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace IntegrationTest
{
    public class TestCase
    {
        public TestCase(TestCaseConf conf)
        {
            mCaseConf = conf;
            mName = conf.Name;
            Status = TestCaseStatus.None;
        }

        private string mName;

        private TestCaseConf mCaseConf;

        private Collect mCollect = new Collect();

        private IList<TestItem> mTestedItems = new List<TestItem>();

        private List<TestItem> mErrorItems = new List<TestItem>();

        private IList<SecondCounter> mSecondHistory = new List<SecondCounter>();

        private List<Exception> mLstExceptions = new List<Exception>();

        protected void NextTestItem()
        {
            int tick = new Random((int)(DateTime.Now.Ticks << 32 >> 32)).Next() % 5 * CaseConf.Sleep;
            if (tick == 0)
                tick = CaseConf.Sleep;
            System.Threading.Thread.Sleep(tick);
        }

        private Queue<IList<TestItem>> GetTestItems()
        {
            Queue<IList<TestItem>> result = new Queue<IList<TestItem>>();
            int count = CaseConf.Count;
            IList<TestItem> tests;
            Type type = Type.GetType(CaseConf.Type);
            int length = 0;
            TestItem.DataBinder db = new TestItem.DataBinder(type, CaseConf.Params);
            while (count > 0)
            {
                tests = new List<TestItem>();
                int utils = (int)(new Random((int)(DateTime.Now.Ticks << 32 >> 32)).Next() % 5) * CaseConf.Units;
                if (utils == 0)
                    utils = CaseConf.Units;
                while (utils > 0 && count > 0)
                {
                    TestItem item = (TestItem)Activator.CreateInstance(type);
                    db.Bind(item);
                    tests.Add(item);
                    count--;
                    utils--;
                    length++;
                }
                if (tests.Count > 0)
                    result.Enqueue(tests);
            }

            return result;
        }

        private bool mStart = false;

        private System.Threading.Timer mTimer;

        private int mTestCount = 0;

        private int mErrors = 0;

        private int mCompeteds = 0;

        private int mLastErrors = 0;

        private int mLastTestCount = 0;

        private InvokeHandler mInvokeHandler;

        private DateTime mStartTime;

        private DateTime mEndTime;

        private bool mIsCompleted = false;

        private double mMinDelay = -1;

        private double mMaxDelay = 0;

        public double MinDelay
        {
            get
            {
                return mMinDelay;
            }
        }

        public double MaxDelay
        {
            get
            {
                return mMaxDelay;
            }
        }

        private void OnExecute(object state)
        {
            mIsCompleted = false;
            DateTime dt;
            Status = TestCaseStatus.Init;
            if (mCaseConf.Count > 0)
            {
                Queue<IList<TestItem>> items = GetTestItems();
                mStartTime = DateTime.Now;
                mCollect.StartTime = mStartTime;
                dt = mStartTime.Add(new TimeSpan(0, CaseConf.RunTime, 0));
                Status = TestCaseStatus.Runing;            
                while (items.Count > 0 && dt > DateTime.Now && mStart)
                {
                    IList<TestItem> testitems = items.Dequeue();
                    ExecuteItems(testitems, dt);
                    NextTestItem();
                }
            }
            else
            {
                Type type = Type.GetType(CaseConf.Type);
                TestItem.DataBinder db = new TestItem.DataBinder(type, CaseConf.Params);
                mStartTime = DateTime.Now;
                mCollect.StartTime = mStartTime;
                dt = mStartTime.Add(new TimeSpan(0, CaseConf.RunTime, 0));

                Status = TestCaseStatus.Runing;
               
                while (dt > DateTime.Now && mStart)
                {
                    IList<TestItem> testitems = new List<TestItem>();
                    int utils = (int)(new Random((int)(DateTime.Now.Ticks << 32 >> 32)).Next() % 5) * CaseConf.Units;
                    if (utils == 0)
                        utils = CaseConf.Units;
                    for (int i = 0; i < utils; i++)
                    {
                        TestItem item = (TestItem)Activator.CreateInstance(type);
                        db.Bind(item);
                        testitems.Add(item);
                    }
                    ExecuteItems(testitems, dt);
                    NextTestItem();

                }

            }
            mEndTime = DateTime.Now;
            CollectInfo.Errors = mErrors;
            CollectInfo.Completeds = mCompeteds;
            CollectInfo.EndTime = DateTime.Now;
            CollectInfo.Counters = mCaseConf.Counters;
            foreach (TestItem item in mTestedItems)
            {
                foreach (TestCounterConf counter in CollectInfo.Counters)
                {
                    if (item.UserTime >= counter.From && item.UserTime < counter.To)
                    {
                        counter.Count++;
                        break;
                    }
                }
            }
            mIsCompleted = true;
            if (TestCompleted != null)
                TestCompleted(this);
            if (mTimer != null)
            {
                mTimer.Dispose();
                mTimer = null;
            }

            Status = TestCaseStatus.Completed;
        }

        private void ExecuteItems(IList<TestItem> items, DateTime dt)
        {

            for (int i = 0; i < items.Count; i++)
            {
                if (dt < DateTime.Now)
                    return;
                items[i].EndTime = dt;
                if (mInvokeHandler.Add(items[i]))
                {
                    mInvokeHandler.Execute();
                    mInvokeHandler.Clean();
                }
            }
            if (mInvokeHandler.Items.Count > 0)
            {
                mInvokeHandler.Execute();
                mInvokeHandler.Clean();
            }
        }

        private void ItemCompleted(TestItem item)
        {
            lock (mTestedItems)
            {
                if (Status == TestCaseStatus.Runing)
                {
                    if (mMinDelay == -1 || item.UserTime < mMinDelay)
                    {
                        mMinDelay = item.UserTime;
                    }
                    if (item.UserTime > mMaxDelay)
                        mMaxDelay = item.UserTime;

                    System.Threading.Interlocked.Increment(ref mTestCount);
                    TestedItems.Add(item);
                    if (item.LastError != null)
                    {
                        ErrorItems.Add(item);
                        lock (mLstExceptions)
                        {
                            mLstExceptions.Add(item.LastError);
                        }
                        System.Threading.Interlocked.Increment(ref mErrors);
                    }
                    else
                    {
                       
                        System.Threading.Interlocked.Increment(ref mCompeteds);
                    }
                }
            }
        }

        public Exception[] GetLstError()
        {
            lock (mLstExceptions)
            {
                try
                {
                    return mLstExceptions.ToArray();
                }
                finally
                {
                    mLstExceptions.Clear();
                }
            }
        }

        public void Stop()
        {
            mStart = false;
            Status = TestCaseStatus.None;
        }

        public void Test()
        {
            if (mInvokeHandler == null)
                mInvokeHandler = new InvokeHandler(CaseConf.Count, ItemCompleted, CaseConf.MaxThread);
            mStart = true;
            mMinDelay = -1;
            TestedItems.Clear();
            ErrorItems.Clear();
            SecondHistory.Clear();
            mTestCount = 0;
            mErrors = 0;
            mCompeteds = 0;
            mLastErrors = 0;
            mLastTestCount = 0;
            mCollect.StartTime = DateTime.Now;
            mCollect.Count = mCaseConf.Count;
            System.Threading.ThreadPool.QueueUserWorkItem(OnExecute);
        }

        public bool Second()
        {
            if (Status == TestCaseStatus.Runing)
            {
                SecondCounter scount = new SecondCounter();
                scount.DateTime = DateTime.Now;
                scount.Count = mTestCount - mLastTestCount;
                scount.Errors = mErrors - mLastErrors;
                scount.Success = scount.Count - scount.Errors;
                mLastErrors = mErrors;
                mLastTestCount = mTestCount;
                mSecondHistory.Add(scount);
                return true;
            }
            return Status != TestCaseStatus.Completed;
        }

        public TestCaseConf CaseConf
        {
            get
            {
                return mCaseConf;
            }
        }

        public Action<TestCase> TestCompleted;

        public IList<SecondCounter> SecondHistory
        {
            get
            {
                return mSecondHistory;
            }
        }

        public List<TestItem> ErrorItems
        {
            get
            {
                return mErrorItems;
            }
        }

        public IList<TestItem> TestedItems
        {
            get
            {
                return mTestedItems;
            }
        }

        public Collect CollectInfo
        {
            get
            {
                return mCollect;
            }
        }

        private string GetName(string name)
        {
            int spaclength = GetLength(name);
            return (new string(' ', (20 - spaclength)) + name + ":");
        }

        private int GetLength(string value)
        {
            int result = 0;
            for (int i = 0; i < value.Length; i++)
            {
                if ((int)value[i] >= 0x4E00 && (int)value[i] <= 0x9FA5)
                {
                    result += 2;
                }
                else
                {
                    result++;
                }
            }
            return result;
        }

        public void ToXML(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("case");

            writer.WriteAttributeString("name", mName);

            writer.WriteStartElement("statistics");

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "测试数量");
            writer.WriteAttributeString("value", mCaseConf.Count.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "测试时长");
            writer.WriteAttributeString("value", mCaseConf.RunTime.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "开始时间");
            writer.WriteAttributeString("value", mStartTime.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "完成时间");
            writer.WriteAttributeString("value", mEndTime.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "耗时");
            writer.WriteAttributeString("value", (mEndTime - mStartTime).ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "测试线程数");
            writer.WriteAttributeString("value", mCaseConf.MaxThread.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "间隔测试时间");
            writer.WriteAttributeString("value", string.Format("{0}-{1}", mCaseConf.Sleep, mCaseConf.Sleep * 5));
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "测试并发区间");
            writer.WriteAttributeString("value", string.Format("{0}-{1}", mCaseConf.Units, mCaseConf.Units * 5));
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "完成情况");
            writer.WriteAttributeString("value", (mCompeteds + mErrors).ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "成功数量");
            writer.WriteAttributeString("value", (mCompeteds).ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "失败数量");
            writer.WriteAttributeString("value", (mErrors).ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "平均秒请求数");
            writer.WriteAttributeString("value", ((double)mTestCount / (double)(mEndTime - mStartTime).TotalSeconds).ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "最大耗时");
            writer.WriteAttributeString("value", MaxDelay.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("item");
            writer.WriteAttributeString("name", "最小耗时");
            writer.WriteAttributeString("value", MinDelay.ToString());
            writer.WriteEndElement();

            writer.WriteEndElement();

            writer.WriteStartElement("collect");
            foreach (TestCounterConf item in CollectInfo.Counters)
            {
                writer.WriteStartElement("item");
                writer.WriteAttributeString("name", item.Name);
                writer.WriteAttributeString("value", item.Count.ToString());
                writer.WriteEndElement();

            }
            writer.WriteEndElement();

            writer.WriteStartElement("second_statistical");
            foreach (SecondCounter item in this.SecondHistory)
            {
                writer.WriteStartElement("item");
                writer.WriteAttributeString("name", item.DateTime.ToString());
                writer.WriteAttributeString("success", item.Count.ToString());
                writer.WriteAttributeString("error", item.Errors.ToString());
                writer.WriteEndElement();
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            int scounter = 0;
            string name = mName;
            if (name.Length % 2 != 0)
                name += " ";
            sb.Append(new string('*', (60 - name.Length) / 2));
            sb.Append(name);
            sb.Append(new string('*', (60 - name.Length) / 2));
            sb.AppendLine("\r\n");
            if (mSecondHistory.Count > 0)
                scounter = mSecondHistory[mSecondHistory.Count - 1].Count;
            sb.AppendFormat("{0}{1}\r\n", GetName("状态"), Status.ToString());
            sb.AppendFormat("{0}{1}\r\n", GetName("测试数量"), mCaseConf.Count);
            sb.AppendFormat("{0}{1}分钟\r\n", GetName("测试时长"), mCaseConf.RunTime);
            sb.AppendFormat("{0}{1}\r\n", GetName("开始时间"), mStartTime);
            if (mIsCompleted)
            {
                sb.AppendFormat("{0}{1}\r\n", GetName("完成时间"), mEndTime);
                sb.AppendFormat("{0}{1}\r\n", GetName("耗时"), (mEndTime - mStartTime));
            }
            else
            {
                sb.AppendFormat("{0}{1}\r\n", GetName("当前时间"), DateTime.Now);
            }
            sb.AppendFormat("{0}{1}\r\n", GetName("测试线程数"), mCaseConf.MaxThread);
            sb.AppendFormat("{0}{1}-{2}毫秒\r\n", GetName("间隔测试时间"), mCaseConf.Sleep, mCaseConf.Sleep * 5);
            sb.AppendFormat("{0}{1}-{2}\r\n", GetName("测试并发区间"), mCaseConf.Units, mCaseConf.Units * 5);
            sb.AppendFormat("{0}{1}\r\n", GetName("完成情况"), mCompeteds + mErrors);
            sb.AppendFormat("{0}{1}\r\n", GetName("成功"), mCompeteds);
            sb.AppendFormat("{0}{1}\r\n", GetName("失败"), mErrors);
            if (mIsCompleted)
            {
                sb.AppendFormat("{0}{1:#####.##}/秒\r\n", GetName("平均秒请求数"), ((double)mTestCount / (double)(mEndTime - mStartTime).TotalSeconds));
                sb.AppendFormat("{0}{1:#####.##}/毫秒\r\n", GetName("最大耗时"), MaxDelay);
                sb.AppendFormat("{0}{1:#####.##}/毫秒\r\n", GetName("最小耗时"), MinDelay);
                sb.AppendFormat("{0}\r\n", GetName("汇总"));
                foreach (TestCounterConf item in CollectInfo.Counters)
                {
                    sb.AppendFormat("{0}{1}\r\n", GetName(item.Name + "/毫秒"), item.Count);
                }

            }
            else
            {

                sb.AppendFormat("{0}{1}/s\r\n", GetName("秒请求数"), scounter);
            }
            sb.AppendLine("\r\n" + new string('*', 60) + "\r\n");
            return sb.ToString();
        }

        public TestCaseStatus Status
        {
            get;
            private set;
        }

        public int GetCount()
        {
            return mTestCount;
        }

        public int GetSuccess()
        {
            return mCompeteds;
        }

        public int GetError()
        {
            return mErrors;
        }

        public int GetLastSuccess()
        {
            if (SecondHistory.Count > 0)
                return SecondHistory[SecondHistory.Count - 1].Count;
            return 0;
        }

        public int GetLastError()
        {
            if (SecondHistory.Count > 0)
                return SecondHistory[SecondHistory.Count - 1].Errors;
            return 0;
        }

    }

    public enum TestCaseStatus
    {
        None,
        Init,
        Runing,
        Completed
    }

    class InvokeHandler
    {
        public InvokeHandler(int count, Action<TestItem> completed, int threadcount)
        {
            mCount = threadcount;
            mCompleted = completed;

        }



        private Action<TestItem> mCompleted;

        private int mCount;

        private List<TestItem> mItems = new List<TestItem>();

        public List<TestItem> Items
        {
            get
            {
                return mItems;
            }
        }

        public void Clean()
        {
            Items.Clear();
        }

        public bool Add(TestItem item)
        {
            Items.Add(item);
            return Items.Count == mCount;
        }

        private int mCounter = 0;

        private void OnExecute(object state)
        {
            InvokeItem ii = (InvokeItem)state;
            ii.Item.Execute();
            mCompleted(ii.Item);
            System.Threading.Interlocked.Decrement(ref mCounter);
        }
        public void Execute()
        {
            mCounter = Items.Count;
            for (int i = 0; i < Items.Count; i++)
            {
                InvokeItem ii = new InvokeItem();
                ii.Item = Items[i];
                System.Threading.ThreadPool.QueueUserWorkItem(OnExecute, ii);
            }
            while (mCounter > 0)
            {
                System.Threading.Thread.Sleep(10);
            }

        }

        public class InvokeItem
        {
            public TestItem Item
            {
                get;
                set;
            }



        }
    }
}
